/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/delay.h>

#include "ring.h"

/*
 * This will compute the number of nops for the ring prefetch size alignment
 * requirement which is defined by pf_dw_mask. Presume the nop dws_n is 1.
 */
static u32 nops_count(u32 pf_dws_mask, u32 wptr, u32 dws_n)
{
	u32 nops_n;

	nops_n = 0;
	wptr += dws_n;
	while (wptr & pf_dws_mask) {
		++nops_n;
		++wptr;
	}
	return nops_n;
}

/* we pass wptr because we don't want to compute it again */
static u32 free_dws_count(struct ring *r, u32 wptr)
{
	u32 rptr;

	rptr = r->rptr_dw_get(r->dev);

	/* we presume the (ring_dws_n * 2) is smaller than u32_max */
	if (rptr <= wptr)
		rptr += r->ring_dws_n;

	return rptr - wptr;
}

long ring_wait(struct ring *r, u32 dws_n, u32 timeouts_max_n, u32 timeout_us)
{
	u32 rptr;
	u32 wptr;
	u32 free_dws_n;
	u32 total_dws_n;
	u32 nops_n;

	wptr = r->wptr_dw_get(r->dev);
	
	nops_n = nops_count(r->pf_dw_mask, wptr, dws_n);
	total_dws_n = dws_n + nops_n;
	
	rptr = r->rptr_dw_get(r->dev);

	free_dws_n = free_dws_count(r, wptr);
	while (free_dws_n < total_dws_n) {
		if (timeouts_max_n == 0)
			return -RING_WAIT_TIMEOUT;
		timeouts_max_n--;

		usleep_range(timeout_us, timeout_us);
		free_dws_n = free_dws_count(r, wptr);
	}
	return 0;
}
